from django.shortcuts import render,redirect
from django.urls.base import reverse
from django.http import JsonResponse
from django.views.generic import View
from goods.models import GoodsSKU
from user.models import Address
from order.models import OrderInfo,OrderGoods
from django_redis import get_redis_connection
from django.contrib.auth.decorators import login_required
from utils.LoginRequireMixin import LoginRequiredMixin
from datetime import datetime
from django.db import transaction # MySQL事务管理
from alipay import AliPay
from alipay import AliPay # 支付宝sdk
from alipay.utils import AliPayConfig
from django.conf import settings
import os
import json

# Create your views here.
# 调用类方法 不登陆到不了这个站点
class Cart_order(LoginRequiredMixin,View):
    def post(self,request):
        # 获取用户
        user = request.user
        # 接收数据
        sku_id_list = request.POST.getlist('sku_id')
        # 验证数据，如果没有那么跳转到购物车页面
        if not sku_id_list:
            return redirect(reverse('cart:cart'))
        # 提交订单需要
        sku_ids = (',').join(sku_id_list)
        conn = get_redis_connection('history')
        cart_key = 'cart_%d'%user.id

        goods_list = []
        total_price = 0
        for sku_id in sku_id_list:
            # 获取对应的商品
            goods = GoodsSKU.objects.get(id=sku_id)
            # 获取商品数量
            count = conn.hget(cart_key,sku_id)
            # 计算商品的小计
            amount = goods.price*int(count)
            # 动态添加对象
            goods.count = int(count)
            goods.amount = amount
            # 追加
            goods_list.append(goods)
            # 计算总价格
            total_price += amount

        # 运费，也有一个子系统来计算运费 但是我在这里简单的写10元
        freight = 10

        # 总工件数
        total_count = len(goods_list)

        # 实际付款
        real_pay = total_price + freight
        # 获取用户的默认收货地址
        address  = Address.objects.filter(user=user)

        context={
            'goods_list':goods_list,
            'total_price':total_price,
            'total_count':total_count,
            'real_pay':real_pay,
            'freight':freight,
            'address':address,
            'sku_ids':sku_ids,
        }

        return render(request, 'cart/order.html',context=context)

# 订单生成
class Commit(View):
    # 加上下面的装饰语句 这个方法里面的所有mysql语句都放入到一个事务当中
    @transaction.atomic
    def post(self, request):
        # 检查用户登录
        user = request.user
        if not user.is_authenticated:
            return JsonResponse({'res':0,'errmsg':'用户未登录'})
        
        # 获取数据 数据有地址，付款方式和商品列表字符串
        address = request.POST.get('address')
        payment_method = request.POST.get('payment_method')
        sku_ids = request.POST.get('sku_ids')

        # 检查是否完整
        if not all([address, payment_method, sku_ids]):
            return JsonResponse({'res':1,'errmsg':'数据不完整'})
        
        # 校验付款方式是否合法
        if payment_method not in OrderInfo.PAY_METHODS.keys():
            return JsonResponse({'res':2,'errmsg':'非法支付方式'})

        # 校验地址
        try:
            addr = Address.objects.get(id=address)
        except:
            return JsonResponse({'res':3,'errmsg':'非法地址'})
        


        # todo： 创建订单的核心业务
        # mysql的事务概念 就是一堆操作为一组 只要其中一个失败整个就失败
        # 下面的商品的库存的检验显得多余，因为在购物车那里已经阻止用户购买数量大于库存 但是注意 咱们这个系统并不是只有一个用户 他有很多用户顾客的 所以如果两个用户向买同一个产品都到了提交订单页面 然后一个先买了 库存改变了 另一个用户点击购买的时候商品库存就没了
        # 然后库存不足这个订单失败 但是在df_order_info这个表里面已经添加了一个空记录 所以避免操作中间出问题 把前面的操作都失效 所以用到mysql的事务概念

        # 组织参数，把往df_order_info里面添加记录所需的参数组织出来
        # order_id 订单号
        order_id = datetime.now().strftime("%Y%m%d%H%M%S")+str(user.id)
        # 运费
        freight = 10
        # 总数目和总金额 先写死 后来计算出来之后再给他改掉
        total_count = 0
        total_price = 0

        # 设置保存点 后面出现错误 回滚到这个位置
        save_id = transaction.savepoint()

        try:
            # todo: 向df_order_info表中添加记录
            order = OrderInfo.objects.create(order_id=order_id,
                                            user=user,
                                            addr=addr,
                                            pay_method=payment_method,
                                            total_count=total_count,
                                            total_price=total_price,
                                            transit_price=freight)
            
            #  todo:用户订单内有多少商品就得往df_order_goods表中添加多少记录
            conn = get_redis_connection('history')
            cart_key = 'cart_%d'%user.id

            sku_ids = sku_ids.split(',')
            # 遍历添加
            for sku_id in sku_ids:
                for i in range(3):
                    # 获取商品
                    try:
                        # mysql语句 ： select * from df_goods_sku where id=sku_id fro update;
                        goods = GoodsSKU.objects.get(id=sku_id)
                        # 下面一行为悲观锁
                        # goods = GoodsSKU.objects.select_for_update().get(id=sku_id) # 加上悲观锁 并发情况下只能拿到锁的进程先执行操作 等执行完释放锁之后其他进程才能继续操作 也就是一次只能提供一个用户服务
                    except:
                        # 如果出错 回滚到保存点
                        transaction.savepoint_rollback(save_id)
                        return JsonResponse({'res':4,'errmsg':'商品不存在'})
                    
                    # 从redis数据库里面获取数目
                    count = conn.hget(cart_key,sku_id)

                    # 俩用户先后点击 库存变化 会导致后面用户库存不足
                    if int(count)>goods.stock:
                        # 如果出错 回滚到保存点
                        transaction.savepoint_rollback(save_id)
                        return JsonResponse({'res':6,'errmsg':'商品库存不足'})

                    # 更新商品库存和销量
                    orig_stock = goods.stock
                    new_stock = orig_stock - int(count)
                    new_sales = goods.sales + int(count)

                    # 乐观锁 也就是按照之前查出来的库存为条件去查后面要更新的数据 如果这个库存在中间被改变了 那就后面更新的时候就找不到库存和之前查询的时候一样的记录 然后就查询条件失败 找不到要更新的记录 最终导致下单失败 我觉得这个也好想没那么安全 因为两个进程刚好卡在两个操作中间 那么问题有出来了 感觉悲观锁更可靠
                    # 但是突然感觉我想的有问题 因为更新操作一定是一先一后 当两个用户的操作刚好卡在了查询和更新之间 但是一个更新完了之后库存就被改变了 当另一个要更新的时候现在的库存和之前查出来的库存不一样了 就失败了 ，就关键在于更新操作只能一个一个来 这样的话下一个更新就会收到上一个更新嗯影响
                    # 但是这个乐观锁还是有缺点的 就是库存多 比如3 两个同时下单 一个成功 另一个因为库存改变了 但是库存有足够的货 失败了 所以得给他多尝试3次
                    # 这里尝试过程中因为mysql事务的默认隔离性 第二个用户第二次尝试的时候拿不到第一个用户第一次操作时更新的数据从而查出来的库存和数据库里面保存的数据库存不一样导致下单失败 所以我们要在MySQL的配置文件当中更改事务的隔离性 transaction_isolation = READ-COMMITTED
                    # 下面一行为乐观锁 在乐观锁 更新数据的时候会查询我们规定的参数 在这里就是库存 如果更新时查出来的数据和之前查询的数据不一致 也就是数据被别人动过了会报错订单失败
                    # mysql语句： update df_goods_sku set stock=new_stock,sales=new_sales where id=sku_id and stock = orig_stock
                    res = GoodsSKU.objects.filter(id=sku_id,stock=orig_stock).update(stock=new_stock,sales=new_sales) # 这个语句返回受影响的行数 这里1为成功 0为失败
                    if res==0:
                        if i == 2:
                            # 已经尝试三次都不行 那就下单失败
                            transaction.savepoint_rollback(save_id)
                            return JsonResponse({'res':7,'errmsg':'下单失败'})
                        continue

                    
                    # todo:向df_order_goods表中添加数据
                    OrderGoods.objects.create(order=order,
                                            sku=goods,
                                            count=count,
                                            price=goods.price)
                    
                    # 累计计算总数目和总价格
                    amount = goods.price * int(count)
                    total_price += amount
                    total_count += int(count)

                    # 成功了 那就跳出循环
                    break

            # 更新df_order_info里面的总价格和总数目
            order.total_price = total_price
            order.total_count = total_count
            order.save()
        except Exception as e:
            # 整个数据库操作当中出现错误都回滚到保存点
            transaction.savepoint_rollback(save_id)
            return JsonResponse({'res':7,'errmsg':'下单失败'})

        # 上面全部没有问题 提交事务
        transaction.savepoint_commit(save_id)

        # 清楚购物车当中对应的商品 sku_ids是列表[1,2,3] 前面加一个* 就按照参数格式传进去 1,2,3
        conn.hdel(cart_key,*sku_ids)

        return JsonResponse({'res':5,'message':'创建成功'})


# 订单支付宝支付后台处理
# ajax post请求
# 前段传过来的数据：order_id
# order/pay
class Alipay_view(View):
    def post(self, request):
        # 验证用户是否登录
        user = request.user
        if not user.is_authenticated:
            return JsonResponse({'res':0,'errmsg':'请登录'})

        # 接受参数
        order_id = request.POST.get('order_id')

        # 校验参数
        if not order_id:
            return JsonResponse({'res':1,'errmsg':'数据不完整'})

        try:
            order = OrderInfo.objects.get(order_id=order_id,
                                            user=user,
                                            pay_method=3,
                                            order_status=1)
        except:
            return JsonResponse({'res':2,'errmsg':'订单错误'})

        # 业务处理：调用python sdk调用支付宝付款接口

        app_private_key_string = open(settings.ALIPAY_PRIVATE_KEY).read()
        alipay_public_key_string = open(settings.ALIPAY_PUBLIC_KEY).read()

        alipay = AliPay(
            appid=settings.ALIPAY_APP_ID,
            app_notify_url=None,  # 默认回调 url
            app_private_key_string=app_private_key_string,
            # 支付宝的公钥，验证支付宝回传消息使用，不是你自己的公钥,
            alipay_public_key_string=alipay_public_key_string,
            sign_type="RSA2",  # RSA 或者 RSA2
            debug=True,  # 默认 False
            verbose=True,  # 输出调试数据
            config=AliPayConfig(timeout=15)  # 可选，请求超时时间
        )

        subject = "A技术有限公司测试订单"
        total_price = order.total_price + order.transit_price
        # 电脑网站支付，需要跳转到：https://openapi.alipay.com/gateway.do? + order_string
        order_string = alipay.api_alipay_trade_page_pay(
            out_trade_no=order_id,
            total_amount=str(total_price),
            subject=subject,
            return_url=None,
            notify_url=None # 可选，不填则使用默认 notify url
        )

        try:
            pay_url = 'https://openapi.alipaydev.com/gateway.do?' + order_string
        except:
            return JsonResponse({'res':4,'errmsg':'sdk错误'})

        # 返回应答
        return JsonResponse({'res':3,'pay_url':pay_url})

# ajax post请求
# 前段传过来的数据：order_id
# order/check
class Alipay_check(View):
    def post(self, request):
        # 验证用户是否登录
        user = request.user
        if not user.is_authenticated:
            return JsonResponse({'res':0,'errmsg':'请登录'})

        # 接受参数
        order_id = request.POST.get('order_id')

        # 校验参数
        if not order_id:
            return JsonResponse({'res':1,'errmsg':'数据不完整'})

        try:
            order = OrderInfo.objects.get(order_id=order_id,
                                            user=user,
                                            pay_method=3,
                                            order_status=1)
        except:
            return JsonResponse({'res':2,'errmsg':'订单错误'})

        # 业务处理：调用python sdk调用支付宝付款接口
        # 初始化
        app_private_key_string = open(settings.ALIPAY_PRIVATE_KEY).read()
        alipay_public_key_string = open(settings.ALIPAY_PUBLIC_KEY).read()

        alipay = AliPay(
            appid=settings.ALIPAY_APP_ID,
            app_notify_url=None,  # 默认回调 url
            app_private_key_string=app_private_key_string,
            # 支付宝的公钥，验证支付宝回传消息使用，不是你自己的公钥,
            alipay_public_key_string=alipay_public_key_string,
            sign_type="RSA2",  # RSA 或者 RSA2
            debug=True,  # 默认 False
            verbose=True,  # 输出调试数据
            config=AliPayConfig(timeout=15)  # 可选，请求超时时间
        )

        # 调用支付宝的查询接口

        while True:
            response = alipay.api_alipay_trade_query(out_trade_no=order_id)

            # response = {
            #         "trade_no": "2017032121001004070200176844", 支付宝交易号
            #         "code": "10000", 状态码
            #         "invoice_amount": "20.00",
            #         "open_id": "20880072506750308812798160715407",
            #         "fund_bill_list": [
            #             {
            #                 "amount": "20.00",
            #                 "fund_channel": "ALIPAYACCOUNT"
            #             }
            #         ],
            #         "buyer_logon_id": "csq***@sandbox.com",
            #         "send_pay_date": "2017-03-21 13:29:17",
            #         "receipt_amount": "20.00",
            #         "out_trade_no": "out_trade_no15",
            #         "buyer_pay_amount": "20.00",
            #         "buyer_user_id": "2088102169481075",
            #         "msg": "Success",
            #         "point_amount": "0.00",
            #         "trade_status": "TRADE_SUCCESS", 支付状态
            #         "total_amount": "20.00"
            # }

            print(response)
            code = response['code']

            if code == '10000' and response['trade_status'] == "TRADE_SUCCESS":
                # 支付成功
                # 获取支付宝交易号
                trade_no = response['trade_no']
                # 更新订单状态
                order.trade_no = trade_no
                order.order_status = 4 # 待评价 中间的过程没有
                order.save()
                # 返回结果
                return JsonResponse({'res':3,'message':'支付成功'})
            elif code == '40004' or (code == '10000' and response['trade_status'] == "WAIT_BUYER_PAY"):
                # 业务失败 可能等一会儿就成功
                # 等待卖家付款
                import time
                time.sleep(5)
                continue
            else:
                # 支付出错
                print(response['code'])
                return JsonResponse({'res':4,'errmsg':'支付失败'})

# 评论页面
# order/comment
class Comment(View):
    # 第一次到评论页面
    def get(self, request,order_id):
        # 验证用户是否登录
        user = request.user
        if not user.is_authenticated:
            return JsonResponse({'res':0,'errmsg':'请登录'})

        # 校验参数 order_id没写 跳转订单页面
        if not order_id:
            return redirect(reverse('user:user_order'))

        # 找不到 跳转到订单页面
        try:
            order = OrderInfo.objects.get(order_id=order_id,
                                            order_status=4)
        except:
            return redirect(reverse('user:user_order'))

        # 获取订单里面的商品
        order_goods_list = OrderGoods.objects.filter(order=order)

        # 动态添加属性
        order.order_goods_list = order_goods_list
        order.status_name = OrderInfo.ORDER_STATUS[order.order_status]

        # 计算总计
        total_price = 0

        # 遍历订单行
        for order_goods in order_goods_list:
            # 在订单商品模型类中记录了count的
            order_goods.amount = order_goods.price * order_goods.count
            # 计算总价
            total_price += order_goods.amount
        
        order.total_price = total_price

        context = {'order':order}

        return render(request,'order/comment.html',context=context)

    # ajax post
    # 评论编辑完提交
    # 接受的参数：sku_comment
    def post(self, request,order_id):
        # 验证用户是否登录
        user = request.user
        if not user.is_authenticated:
            return JsonResponse({'res':0,'errmsg':'请登录'})

        # 获取参数
        sku_comment = request.POST.get('sku_comment')

        # 传来的使json数据 需要进行转换
        sku_comment = json.loads(sku_comment)

        # 校验参数 空的话提示
        if not sku_comment or len(sku_comment) == 0:
            return JsonResponse({'res':1,'errmsg':'数据不完整'})

        # 校验参数 order_id没写 跳转订单页面
        if not order_id:
            return redirect(reverse('user:user_order'))

        # 找不到 跳转到订单页面
        try:
            order = OrderInfo.objects.get(order_id=order_id,
                                            order_status=4)
        except:
            return redirect(reverse('user:user_order'))

        for sku_id,comment in sku_comment.items():
            # 获取商品
            try:
                sku =  GoodsSKU.objects.get(id=sku_id)
            except:
                # 如果找不到这个商品 那么下面打操作不做了 继续循环
                continue
            
            # 获取订单里面的商品
            try:
                order_goods = OrderGoods.objects.get(order=order,sku=sku)
            except:
                continue

            # 都找到了 到了这个步骤 那就是添加评论了
            order_goods.comment = comment
            order_goods.save()

        # 更改订单状态
        order.order_status = 5 # 已完成
        order.save()

        # 返回应答
        return JsonResponse({'res':2,'message':'成功评论'})
